/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.connector.base.protocol;

import com.alibaba.fastjson2.JSONObject;
import com.google.common.base.Charsets;
import com.je.connector.base.exception.UnKnownCommandException;
import io.netty.buffer.ByteBuf;
import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@ToString
@NoArgsConstructor
public class Packet {

    /**
     * 消息头长度
     */
    public static final int HEADER_LEN = 10;

    /**
     * 正常请求类型
     */
    public static final byte NORMAL_REQ_TYPE = 0;

    /**
     * 正常响应类型
     */
    public static final byte NORMAL_RESP_TYPE = 1;

    /**
     * 集群请求类型
     */
    public static final byte CLUSTER_REQ_TYPE = 2;

    /**
     * 集群响应类型
     */
    public static final byte CLUSTER_RESP_TYPE = 4;

    /**
     * 心跳请求
     */
    public static final Packet HEARTBEAT_REQ_PACKET = new Packet(Command.HEARTBEAT.cmd);
    /**
     * 心跳响应
     */
    public static final Packet HEARTBEAT_RESP_PAFKET = new Packet(Command.HEARTBEAT.cmd, (byte) 1);

    /**
     * 消息体长度
     */
    private int bodyLength;
    /**
     * 命令类型
     */
    private byte cmd;
    /**
     * 命令类型的类型
     * 请求:0
     * 响应:1
     * 集群转发请求：2
     * 集群转发响应：4
     * 心跳：5
     * 其他类型
     * 如果是集群转发过来的请求和相应则不要再转发了
     */
    private byte type;
    /**
     * 会话ID
     */
    private int sessionId;

    /**
     * 标识当前启用的特性
     * 0 无任何特性
     * 1 压缩
     * 2 加密
     */
    private byte flags;
    /**
     * 消息体
     */
    private byte[] body;

    public Packet(byte cmd) {
        this.cmd = cmd;
        this.sessionId = 0;
        this.type = 0;
        this.bodyLength = 0;
        this.flags = 0;
    }

    public Packet(byte cmd, byte type) {
        this.cmd = cmd;
        this.type = type;
        this.bodyLength = 0;
        this.flags = 0;
    }

    public Packet(byte cmd, byte type, int sessionId) {
        this.cmd = cmd;
        this.type = type;
        this.sessionId = sessionId;
        this.bodyLength = 0;
        this.flags = 0;
    }

    public Packet(byte cmd, byte type, int sessionId, int bodyLength) {
        this.cmd = cmd;
        this.type = type;
        this.sessionId = sessionId;
        this.bodyLength = bodyLength;
        this.flags = 0;
    }

    public boolean hasFlag(byte flag) {
        return (flags & flag) != 0;
    }

    public void addFlag(byte flag) {
        this.flags |= flag;
    }

    /**
     * 是请求或响应
     *
     * @return true是请求，false是响应
     */
    public boolean isReq() {
        return (this.type | (byte) 0) == NORMAL_REQ_TYPE
                || (this.type | (byte) 0) == CLUSTER_REQ_TYPE;
    }

    /**
     * 是否是响应
     *
     * @return
     */
    public boolean isResp() {
        return (this.type | (byte) 0) == NORMAL_RESP_TYPE
                || (this.type | (byte) 0) == CLUSTER_RESP_TYPE;
    }

    /**
     * 集群转发过来的请求
     *
     * @return
     */
    public boolean isClusterReq() {
        return (this.type | (byte) 0) == CLUSTER_REQ_TYPE;
    }

    /**
     * 是否是集群转发过来的响应，应该直接返回给连接
     *
     * @return
     */
    public boolean isClusterResp() {
        return (this.type | (byte) 0) == CLUSTER_RESP_TYPE;
    }

    /**
     * 转换Packet成为集群请求
     */
    public void convertPacketToClusterReq() {
        if (isReq() && !isClusterReq()) {
            this.setType(CLUSTER_REQ_TYPE);
        }
    }

    /**
     * 转换Packet成为集群响应
     *
     * @return
     */
    public void convertPacketToClusterResp() {
        if (isResp() && !isClusterResp()) {
            this.setType(CLUSTER_RESP_TYPE);
        }
    }

    public JSONObject getTargetBody() {
        return JSONObject.parseObject(new String(getBody(), Charsets.UTF_8));
    }

    public void setTargetBody(JSONObject body) {
        setBody(body.toJSONString().getBytes(Charsets.UTF_8));
    }

    /**
     * 解码
     *
     * @param packet
     * @param in
     * @param bodyLength
     * @return
     */
    public static Packet decodePacket(Packet packet, ByteBuf in, int bodyLength) {
        packet.setBodyLength(bodyLength);
        packet.setCmd(in.readByte());
        packet.setType(in.readByte());
        packet.setSessionId(in.readInt());
        packet.setFlags(in.readByte());
        if (packet.getBodyLength() > 0) {
            in.readBytes(packet.getBodyLength());
        }
        return packet;
    }

    /**
     * 编码
     *
     * @param packet
     * @param out
     */
    public static void encodePacket(Packet packet, ByteBuf out) {
        if (packet.getCmd() == Command.UNKNOWN.cmd) {
            throw new UnKnownCommandException("connector receive unknown command");
        } else {
            out.writeInt(packet.getBodyLength());
            out.writeByte(packet.getCmd());
            out.writeByte(packet.getType());
            out.writeInt(packet.getSessionId());
            out.writeByte(packet.getFlags());
            if (packet.getBodyLength() > 0) {
                out.writeBytes(packet.getBody());
            }
        }
    }

    public Packet response(Command command) {
        return new Packet(command.cmd, NORMAL_RESP_TYPE, sessionId);
    }

}
